
namespace SharpTalk.Core
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.AccessControl;
    using System.Text;
    using System.Threading;
    using Core.Interfaces;

    /// <summary>
    /// 
    /// </summary>
    public sealed class MessageLogger : IMessageLogger
    {
        private bool _disposed;
        private FileStream _stream;
        //private ReaderWriterLock _lock;
        private ReaderWriterLockSlim _lockSlim;
        private const int LOCK_TIMEOUT = 5000;

        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="file">THe file used to save messages</param>
        /// <exception cref="ArgumentNullException">file is null or empty</exception>
        public MessageLogger(string file)
        {
            if(string.IsNullOrEmpty(file))throw new ArgumentNullException("file");
            
            _stream = new FileStream(
                file,
                FileMode.Append,
                FileSystemRights.Write,
                FileShare.Read,
                4096,
                FileOptions.None);

            //_lock = new ReaderWriterLock();
            _lockSlim = new ReaderWriterLockSlim();
        }

        /// <summary>
        /// Adds a message
        /// </summary>
        /// <param name="message"></param>
        /// <exception cref="System.ObjectDisposedException"/>
        public bool AppendMessage(SimpleMessage message)
        {
            ThrowIfDisposed();
            return message !=null ? AppendMessages(new []{ message }) : false ;
        }
        /// <summary>
        /// Adds a list of messages
        /// </summary>
        /// <param name="messages"></param>
        /// <exception cref="System.ObjectDisposedException"/>
        public bool AppendMessages(IEnumerable<SimpleMessage> messages)
        {
            ThrowIfDisposed();
            var success = false;

            _lockSlim.TryEnterWriteLock(LOCK_TIMEOUT);
            //_lock.AcquireWriterLock(LOCK_TIMEOUT);
            try
            {
                foreach (var content in from msg in messages where msg.IsNotMotd select GetUtf8Content(msg.ToXmlString()))
                {
                    _stream.Write(content, 0, content.Length);
                }
                _stream.Flush(true);
                success = true;
            }
            catch (Exception)
            {
                // Ignore exceptions
            }
            finally
            {
                //if(_lock!=null && _lock.IsWriterLockHeld)
                //    _lock.ReleaseWriterLock();

                if(_lockSlim.IsWriteLockHeld)
                    _lockSlim.ExitWriteLock();
            }
            return success;
        }
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if(_disposed) return;
            if (_stream != null)
                _stream.Dispose();

            _stream = null;

            //if(_lock!=null && _lock.IsWriterLockHeld)
            //    _lock.ReleaseLock();
            if (_lockSlim != null)
                _lockSlim.Dispose();

            //_lock = null;
            _lockSlim = null;
            _disposed = true;
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Throws ObjectDisposedException if the instance is disposed
        /// </summary>
        private void ThrowIfDisposed()
        {
            if(_disposed)throw new ObjectDisposedException("Object disposed");
        }

        /// <summary>
        /// Converts a string to an UTF8 byte array
        /// </summary>
        /// <param name="value">The string to convert</param>
        /// <returns>A byte array</returns>
        private static byte[] GetUtf8Content(string value)
        {
            return string.IsNullOrEmpty(value) ? new byte[] { } : Encoding.UTF8.GetBytes(value);
        }
    }
}